#include <strmif.h>
#include "cstreamio.hpp"
#include <vfwmsgs.h>
#include <cassert>
#include <climits>
#include <ctime>
#ifdef _DEBUG
#include "odbgstream.hpp"
using std::endl;
#endif

namespace MkvMux
{


CStreamIO::CStreamIO(IStream* p, const BYTE* h, ULONG n) :
    m_pStream(p),
    m_hdr(h, h + n)
{
    assert(p);
    Init();
}


CStreamIO::~CStreamIO()
{
    Final();
}


void CStreamIO::Init()
{    
    m_max_timecode_ms = 0;  //keep track of duration
    m_cFrames = 0;          //block number within cluster

    SetPosition(0);
    WriteEbmlHeader();
    InitSegment();
}


void CStreamIO::Final()
{
    FinalSegment();

    //Reset duration now, in case GetDuration
    //is called while we're stopped.
    m_max_timecode_ms = 0;
}



__int64 CStreamIO::SetPosition(
    IStream* pStream,
    __int64 move_, 
    STREAM_SEEK origin)
{
    LARGE_INTEGER move;
    move.QuadPart = move_;
    
    ULARGE_INTEGER newpos;
    
    const HRESULT hr = pStream->Seek(move, origin, &newpos);
    assert(SUCCEEDED(hr));
    hr;
    
    return newpos.QuadPart;
}


__int64 CStreamIO::SetPosition(__int64 pos, STREAM_SEEK origin) const
{
    return SetPosition(m_pStream, pos, origin);
}


__int64 CStreamIO::GetPosition() const
{
    return SetPosition(0, STREAM_SEEK_CUR);
}


HRESULT CStreamIO::WriteVideo(IMediaSample* p)
{
    //Note that the caller (really, Inpin::OnReceiveMultiple) will interpet
    //a return of S_FALSE to mean "did not process frame" (and do not
    //send anymore).  In principle, we could have a situation in which
    //we process the frame, and then indicate to not send anymore frames,
    //but we decide by fiat that that case is not allowed. 
    
    //interrogate keyframe status
    //cues should have already been created
    //need cue for this stream
    //blockgroup goes inside cue
    //block goes inside block group???
    //frame goes inside block
        
    //odbgstream os;    
    //os << "CStreamIO::WriteVideo: ";

    if (p == 0)  //EOS
    {
        //os << "EOS" << endl;
        m_parser.SetEOS();
    }
    else
    {    
        //HRESULT hr = p->IsSyncPoint();
        //const bool bKey = (hr == S_OK);
        
        //if (bKey)
        //    os << "KEYFRAME";
        
        //os << endl;
        
        BYTE* ptr;

        const HRESULT hr = p->GetPointer(&ptr);
        assert(SUCCEEDED(hr));
        assert(ptr);
        hr;
                
        const long len = p->GetActualDataLength();
        assert(len >= 0);
        
        m_parser.WriteData(ptr, len);
    }
        
    while (MPEGFrame* f = m_parser.ReadFrame())
        WriteFrame(f);

    //If EOS, then we can write cues now, or defer
    //until file close.
        
    return S_OK;
}


#ifdef _DEBUG
static std::ostream& operator<<(std::ostream& os, const MPEGFrame* f)
{
    assert(f);
    
    os << "(" << f->frameType << ") tc[ms]=" << f->timecode_ms;
    
    if (f->frameType != 'I')
        os << " back[ms]=" << f->firstRef_ms;
        
    if (f->frameType == 'B')
        os << " fwd[ms]=" << f->secondRef_ms;
        
    //os << " dur[ms]=" << f->duration_ms;
            
    return os;
}
#endif


void CStreamIO::WriteFrame(MPEGFrame* f)
{
    assert(f);
    assert(f->timecode_ms >= 0);
    //assert(f->duration_ms <= 0x7E);

#ifdef _DEBUG
    odbgstream os;
    os << "WriteFrame: " << f << endl;
#endif
    
    NewCluster(f);
        
    assert(m_cFrames >= 0);
    assert(m_cFrames < LONG_MAX);
    ++m_cFrames;
    
    //What is size of payload?
    //block = 1 byte ID + 4 byte size + f->size
    //block duration = 1 byte ID + 1 byte size + 1 byte value
    //reference block = 1 byte ID + 2 byte size + 2(?) byte signed value
    
    //We only have 1ms precision in the file (bad dog Matroska, very bad!), 
    //but we maintain
    //1ns precision internally.  Do we have to ensure an exact match?
    //(How else would the decoder identify the referenced frame?)
    //As much as it pains me to do so, we'll probably have to 
    //change MPEGFrame so that timecode etc have ms units.
    
    //Alternatively, we could use a Simple Block, but I can't
    //figure how it's represented from my reading of the specification.
    
    Cluster& c = m_clusters.back();

    const ULONG block_size = 1 + 2 + 1 + f->size;      //tn, tc, flg, f

    ULONG block_group_size = 1 + 4 + block_size; //block id, size, payload
    
    if (f->frameType != 'I')
        block_group_size += 4;
        
    if (f->frameType == 'B')
        block_group_size += 4;
    
    WriteID1(0xA0);                //block group ID
    Write4UInt(block_group_size);  //size of payload for this block group    

#ifdef _DEBUG    
    const __int64 pos = GetPosition();
#endif

    //begin block
        
    WriteID1(0xA1);  //Block ID
    Write4UInt(block_size);

    Write1UInt(1);   //track number
    
    LONG tc_ = f->timecode_ms - c.m_timecode_ms;
    assert(tc_ >= SHRT_MIN);
    assert(tc_ <= SHRT_MAX);
    
    SHORT tc = static_cast<SHORT>(tc_);
    
    Write2SInt(tc);  //relative timecode
    
    Write1UInt(0);  //flags

    Write(f->data, f->size);  //frame
    
    //end block
    
    if (f->frameType == 'I')
    {
        Cluster::keyframes_t& kk = c.m_keyframes;
        const Cluster::Keyframe k(f->timecode_ms, m_cFrames);

        kk.push_back(k);
    }
    else
    {
        tc_ = f->firstRef_ms - f->timecode_ms;
        assert(tc_ <= 0);
        assert(tc_ >= SHRT_MIN);
        
        tc = static_cast<SHORT>(tc_);

        WriteID1(0xFB);
        Write1UInt(2);   //?
        Write2SInt(tc);  //payload: relative to frame

        if (f->frameType == 'B')
        {
            tc_ = f->secondRef_ms - f->timecode_ms;
            assert(tc_ >= 0);
            assert(tc_ <= SHRT_MAX);
            
            tc = static_cast<SHORT>(tc_);

            WriteID1(0xFB);
            Write1UInt(2);   //?
            Write2SInt(tc);  //relative to frame
        }
    }
    
#ifdef _DEBUG
    const __int64 newpos = GetPosition();
    assert((newpos - pos) == block_group_size);
#endif
    
    if (f->timecode_ms > m_max_timecode_ms)
        m_max_timecode_ms = f->timecode_ms;
    
    delete f;
}


void CStreamIO::NewCluster(MPEGFrame* f)
{
    assert(f);
    assert(f->timecode_ms >= 0);

    clusters_t& cc = m_clusters;
    
    if (cc.empty())
    {
        assert(f->frameType == 'I');
        __noop;  //yes, create new cluster
    }    
    else if (f->frameType == 'B')
    {
        //Note that this cannot overflow the relative
        //timecode, because its timecode is always less
        //than that of the reference frame it follows.
        
        return;  //no, do not create a new cluster
    }                
    else
    {
        const Cluster& c = m_clusters.back();
        assert(c.m_timecode_ms >= 0);
        
        const LONG dt = f->timecode_ms - c.m_timecode_ms;
        assert(dt >= 0);
        
        if (dt < 1000)
            return;  //no, do not create a new cluster

        //We desire that the first frame in the cluster
        //be a keyframe, but if a long time can pass before
        //between keyframes, then overflow would result
        //when calculating the relative timecode.  This
        //forces us to create a new cluster sooner than
        //we would like.
                    
        if (dt > SHRT_MAX)
            __noop;  //yes, create a new cluster

        else if (f->frameType == 'P')
            return;  //no, do not create a new cluster
    }
        
    cc.push_back(Cluster());
    
    Cluster& c = m_clusters.back();
    
    c.m_pos = GetPosition();
    c.m_timecode_ms = f->timecode_ms;
    
    m_cFrames = 0;

    WriteID4(0x1F43B675);   //Cluster ID
    Write4UInt(0);  //patch size later, during close
    
    //timecode
    
    WriteID1(0xE7);
    WriteData4UInt(c.m_timecode_ms);    
    
    //position
    //TODO
    
    //prevsize
    //TODO
    
    //blockgroup[n]
    //see caller, WriteFrame        
}


#if 0
void CStreamIO::WriteFrame(MPEGFrame* f)
{
    //TODO: we don't really need this anymore.  Frame are arriving
    //here in the correct order (that is, decode order), so there's
    //nothing to do except just stream it out.  We'll have to 
    //make a note of I-frames, for when we build the index.

    assert(f);  //TODO
    
    if ((f == 0) && m_frames.empty())
        return;
        
    assert(f);  //TODO
    
    m_frames.push_back(Frame(f));

    odbgstream os;

    os << "enqueueing frame: " << f << endl;
                       
    if ((f != 0) && (f->frameType == 'I'))
        ++m_cKeys;
        
    assert(f);  //TODO
    
    if ((f->frameType == 'B') || (m_cKeys < 3))
        return;
        
    typedef frames_t::iterator iter_t;

    iter_t i = m_frames.begin();

    iter_t j = --iter_t(m_frames.end());
    assert(j != i);
    assert(j->m_pFrame == f);

    iter_t k = i;
    
    while (k != j)
    {
        Frame& F = *k;
        
        //KaxBlockGroup*& pBG = F.m_pBlock;

//         if (pBG)
//         {
//             ++k;
//             continue;
//         }
        
        f = F.m_pFrame;
        assert(f);

        os << "interrogating frame: " << f << endl;
                   
        if (f->frameType == 'I')
        {
            ++k;
            continue;
        }

        //find frame that corresponds to back ref
        
        iter_t kk = k;
        
        for (;;)
        {
            const Frame& PF = *--kk;
            assert(PF.m_pFrame);
            
            if (f->firstRef == PF.m_pFrame->timecode)
                break;

            assert(kk != i);
        }
        
        const Frame& PF = *kk;  //prev frame (back ref)
        assert(PF.m_pFrame);
        assert(PF.m_pFrame->frameType != 'B');
//         assert(PF.m_pBlock);

//         const KaxBlockGroup* const pPBG = PF.m_pBlock;
//         assert(pPBG);
            
//         assert(!m_clusters.empty());
//         KaxCluster& cluster = m_clusters.back();

        if (f->frameType == 'P')
        {
            ++k;
            continue;
        }    

        kk = k;
        
        for (;;)
        {
            const Frame& NF = *--kk;
            assert(NF.m_pFrame);
            
            if (f->secondRef == NF.m_pFrame->timecode)
                break;

            assert(kk != i);
        }
        
        const Frame& NF = *kk;  //next frame (forward ref)
        assert(NF.m_pFrame);
        assert(NF.m_pFrame->frameType != 'B');
//         assert(NF.m_pBlock);

        ++k;
    }
    
    assert(m_cKeys >= 3);
    
    int cClusters = 0;
    
    while (i != j)
    {
        assert(i->m_pFrame);
        
        if (i->m_pFrame->frameType == 'I')
        {
            if (m_cKeys <= 3)
                break;
                
            --m_cKeys;
            ++cClusters;
        }
        
        os << "deleting frame: " << i->m_pFrame << endl;
                   
        i = m_frames.erase(i);
    }
    
    os << "\nWhat remains in queue\n";
    
    i = m_frames.begin();
    
    while (i != j)
    {
        assert(i->m_pFrame);
        
        os << "remainging frame: " << i->m_pFrame << endl;
        
        ++i;
    }  
}
#endif


#if 0
uint32 CStreamIO::read(void* buf, size_t len)
{
    assert(bool(m_pStream));
    
    const ULONG cb = static_cast<ULONG>(len);
    ULONG cbRead;
    
    const HRESULT hr = m_pStream->Read(buf, cb, &cbRead);
    assert(SUCCEEDED(hr));
    
    return cbRead;
}


void CStreamIO::setFilePointer(int64 pos_, libebml::seek_mode mode_)
{
    assert(bool(m_pStream));
    
    LARGE_INTEGER pos;
    pos.QuadPart = pos_;
    
    STREAM_SEEK mode;
    
    switch (mode_)
    {
        case libebml::seek_beginning:
            mode = STREAM_SEEK_SET;
            break;
            
        case libebml::seek_end:
            mode = STREAM_SEEK_END;
            break;
            
        case libebml::seek_current:
        default:
            mode = STREAM_SEEK_CUR;
            break;
    }
    
    const HRESULT hr = m_pStream->Seek(pos, mode, 0);
    assert(SUCCEEDED(hr));
    
    hr;
}


size_t CStreamIO::write(const void* buf, size_t len)
{
    assert(bool(m_pStream));
    
    const ULONG cb = static_cast<ULONG>(len);
    ULONG cbWritten;
    
    const HRESULT hr = m_pStream->Write(buf, cb, &cbWritten);
    assert(SUCCEEDED(hr));
    
    return cbWritten;
}


uint64 CStreamIO::getFilePointer()
{
    assert(bool(m_pStream));
    
    LARGE_INTEGER zero;
    zero.QuadPart = 0;
    
    ULARGE_INTEGER pos;
    
    const HRESULT hr = m_pStream->Seek(zero, STREAM_SEEK_CUR, &pos);
    assert(SUCCEEDED(hr));
    assert(pos.QuadPart >= 0);
    
    return pos.QuadPart;
}


void CStreamIO::close()
{
    assert(bool(m_pStream));
    
    //TODO: add frames if we didn't see an EOS
    
    typedef clusters_t::iterator iter_t;

    iter_t i = m_clusters.begin();
    const iter_t j = m_clusters.end();    
    
    while (i != j)
    {
        KaxCluster& c = *i++;

	    uint64 ClusterSize = c.Render(*this, m_cues);
	    ClusterSize;
	    
	    m_seekhead.IndexThis(c, m_segment);
	}
	
    //m_clusters.clear();
    
    //The assertion CheckMandatory() is failing.
    //No clue why...
	uint32 CueSize = m_cues.Render(*this);
	CueSize;
	
	m_seekhead.IndexThis(m_cues, m_segment);
	
	m_seekhead.Render(*this);

    m_clusters.clear();

#if 0  //TODO: I don't know how to do this
	KaxInfo& i = GetChild<KaxInfo>(m_segment);
	
	KaxDuration& duration = GetChild<KaxDuration>(i);
	EbmlFloat& d = static_cast<EbmlFloat&>(duration);
	
	d = double(m_prev_time) / 10000000;

    setFilePointer(m_idxpos, libebml::seek_beginning);    
    uint32 InfoSize = i.Render(*this);  //write to file
#endif

    //TODO: complete any EBML header writing
}
#endif


#if 0
void CStreamIO::WriteID(ebml_id_t id)
{
    assert(id <= 0x0FFFFFFE);
    
    id |= 0x10000000;  //always write 4 bytes
    
    BYTE* const q = reinterpret_cast<BYTE*>(&id);
    BYTE* p = q + 3;
    
    for (;;)
    {
        ULONG cbWritten;
    
        const HRESULT hr = m_pStream->Write(p, 1, &cbWritten);
        assert(SUCCEEDED(hr));
        assert(cbWritten == 1);
        
        if (p == q)
            break;
            
        --p;
    }
}
#endif


void CStreamIO::Serialize(
    ISequentialStream* pStream,
    const BYTE* p,
    const BYTE* q)
{
    assert(pStream);
    assert(p);
    assert(q);
    assert(q >= p);
    
    while (q != p)
    {
        --q;
        
        ULONG cbWritten;
    
        const HRESULT hr = pStream->Write(q, 1, &cbWritten);
        assert(SUCCEEDED(hr));
        assert(cbWritten == 1);
        hr;
    }
}


void CStreamIO::Serialize(
    ISequentialStream* pStream,
    const void* buf,
    ULONG len)
{
    const BYTE* const p = static_cast<const BYTE*>(buf);
    const BYTE* const q = p + len;
    
    Serialize(pStream, p, q);
}
    

void CStreamIO::Serialize(
    const void* buf,
    ULONG len)
{
    Serialize(m_pStream, buf, len);
}


void CStreamIO::Write(
    ISequentialStream* pStream, 
    const void* ptr, 
    ULONG cb)
{
    ULONG cbWritten;
    
    const HRESULT hr = pStream->Write(ptr, cb, &cbWritten);
    assert(SUCCEEDED(hr));
    assert(cbWritten == cb);
    hr;
}


void CStreamIO::Write(const void* ptr, ULONG cb)
{
    Write(m_pStream, ptr, cb);
}


void CStreamIO::SetSize(IStream* pStream, __int64 size_)
{
    ULARGE_INTEGER size;
    size.QuadPart = size_;
        
    const HRESULT hr = pStream->SetSize(size);
    assert(SUCCEEDED(hr));
    hr;
}


void CStreamIO::SetSize(__int64 size)
{
    SetSize(m_pStream, size);
}    


void CStreamIO::Write8UInt(ISequentialStream* pStream, __int64 val)
{
    assert(val <= 0x00FFFFFFFFFFFFFE);  //0000 000x 1111 1111 ...
    val |= 0x0100000000000000;          //always write 8 bytes
    
    Serialize(pStream, &val, 8);
}


void CStreamIO::Write8UInt(__int64 val)
{
    Write8UInt(m_pStream, val);
}


void CStreamIO::Write4UInt(ISequentialStream* pStream, ULONG val)
{
    assert(val <= 0x0FFFFFFE);  //000x 1111 1111 ...
    val |= 0x10000000;  //always write 4 bytes
    
    Serialize(pStream, &val, 4);
}


void CStreamIO::Write4UInt(ULONG val)
{
    Write4UInt(m_pStream, val);
}


void CStreamIO::Write2UInt(ISequentialStream* pStream, USHORT val)
{
    assert(val <= 0x3FFE);  //0x11 1111 1111 1110
    val |= 0x4000;          //always write 2 bytes
    
    Serialize(pStream, &val, 2);
}


void CStreamIO::Write2UInt(USHORT val)
{
    Write2UInt(m_pStream, val);
}


void CStreamIO::Write2SInt(ISequentialStream* pStream, SHORT val)
{
    Serialize(pStream, &val, 2);
}


void CStreamIO::Write2SInt(SHORT val)
{
    Write2SInt(m_pStream, val);
}


void CStreamIO::Write1UInt(ISequentialStream* pStream, BYTE val)
{
    assert(val <= 0x7E);  //x111 1110
    val |= 0x80;          //always write 1 byte
    
    Serialize(pStream, &val, 1);
}


void CStreamIO::Write1UInt(BYTE val)
{
    Write1UInt(m_pStream, val);
}


void CStreamIO::WriteID4(ISequentialStream* pStream, ULONG id)
{
    assert(id & 0x10000000);  //always write 4 bytes
    assert(id <= 0x1FFFFFFE);
    
    Serialize(pStream, &id, 4);
}


void CStreamIO::WriteID4(ULONG id)
{
    WriteID4(m_pStream, id);
}

  
void CStreamIO::WriteID3(ISequentialStream* pStream, ULONG id)
{
    assert(id & 0x200000);  //always write 3 bytes
    assert(id <= 0x3FFFFE);
    
    Serialize(pStream, &id, 3);
}


void CStreamIO::WriteID3(ULONG id)
{
    WriteID3(m_pStream, id);
}


void CStreamIO::WriteID2(ISequentialStream* pStream, USHORT id)
{
    assert(id & 0x4000);  //always write 2 bytes
    assert(id <= 0x7FFE);
    
    Serialize(pStream, &id, 2);
}


void CStreamIO::WriteID2(USHORT id)
{
    WriteID2(m_pStream, id);
}


void CStreamIO::WriteID1(ISequentialStream* pStream, BYTE id)
{
    assert(id & 0x80);  //always write 1 byte
    assert(id <= 0xFE);
    
    Serialize(pStream, &id, 1);
}
  

void CStreamIO::WriteID1(BYTE id)
{
    WriteID1(m_pStream, id);
}


void CStreamIO::WriteEbmlHeader()
{
    WriteID4(0x1A45DFA3);
    
    const __int64 start_pos = SetPosition(2, STREAM_SEEK_CUR);

    //EBML Version
    
    WriteID2(0x4286);
    WriteData4UInt(1);

    //EBML Read Version
    
    WriteID2(0x42F7);
    WriteData4UInt(1);
    
    //EBML Max ID Length
    
    WriteID2(0x42F2);
    WriteData4UInt(4);
    
    //EBML Max Size Length
    
    WriteID2(0x42F3);
    WriteData4UInt(8);

    //Doc Type

    WriteID2(0x4282);
    WriteData4String("matroska");
        
    //Doc Type Version
    
    WriteID2(0x4287);
    WriteData4UInt(1);
    
    //Doc Type Read Version
    
    WriteID2(0x4285);
    WriteData4UInt(1);
    
    const __int64 stop_pos = GetPosition();
    
    const __int64 size_ = stop_pos - start_pos;
    assert(size_ <= USHRT_MAX);
    
    const USHORT size = static_cast<USHORT>(size_);
    
    SetPosition(start_pos - 2);
    Write2UInt(size);
    
    SetPosition(stop_pos);
}


void CStreamIO::InitSegment()
{
    m_segment_pos = GetPosition();
    
    WriteID4(0x18538067);  //SeekHead ID
    Write8UInt(0);         //will need to be filled in later
    
    InitSeekHead();  //Meta Seek
    InitInfo();      //Segment Info
    WriteTrack();
}


void CStreamIO::WriteCues()
{
    m_cues_pos = GetPosition();
    
    WriteID4(0x1C53BB6B);   //Cues ID

    const __int64 start_pos = SetPosition(4, STREAM_SEEK_CUR);
    
    typedef clusters_t::const_iterator iter_t;
    
    iter_t i = m_clusters.begin();
    const iter_t j = m_clusters.end();
    
    while (i != j)
    {
        const Cluster& c = *i++;
        c.WriteCuePoints(*this);
    }
    
    const __int64 stop_pos = GetPosition();
    
    const __int64 size_ = stop_pos - start_pos;
    const ULONG size = static_cast<ULONG>(size_);
    
    SetPosition(m_cues_pos + 4);
    Write4UInt(size);
    
    SetPosition(stop_pos);
}


void CStreamIO::Cluster::WriteCuePoints(CStreamIO& f) const
{
    //cue point container = 1 + size len(2) + payload len
    //  time = 1 + size len(1) + payload len(4)
    //  track posns container = 1 + size len + payload len
    //     track = 1 + size len + payload len (track number val)
    //     cluster pos = 1 + size len + payload len (pos val)
    //     block num = 2 + size len + payload len (block num val)
    
    //TODO: for now assume just video
    
    assert(m_keyframes.size() <= 255);
    
    typedef keyframes_t::const_iterator iter_t;
    
    iter_t i = m_keyframes.begin();
    const iter_t j = m_keyframes.end();
    
    while (i != j)
    {
        const Keyframe& k = *i++;
        k.WriteCuePoint(f, *this);
    }
}


void CStreamIO::Cluster::Keyframe::WriteCuePoint(
    CStreamIO& f,
    const Cluster& c) const
{
    f.WriteID1(0xBB);  //CuePoint ID
    f.Write1UInt(28);  //payload size

#ifdef _DEBUG    
    const __int64 start_pos = f.GetPosition();
#endif
    
    f.WriteID1(0xB3);                //CueTime ID
    f.Write1UInt(4);                 //payload len is 4
    f.Serialize(&m_timecode_ms, 4);  //payload
    
    f.WriteID1(0xB7);  //CueTrackPositions
    f.Write1UInt(20);  //payload size

#ifdef _DEBUG    
    const __int64 start_track_pos = f.GetPosition();
#endif
    
    const BYTE track_num = 1;    //TODO: assumes video

    f.WriteID1(0xF7);            //CueTrack ID
    f.Write1UInt(1);             //payload size is 1 byte
    f.Serialize(&track_num, 1);  //payload
    
    const __int64 off = c.m_pos - f.m_segment_pos - 12;
    assert(off >= 0);
    
    f.WriteID1(0xF1);        //CueClusterPosition ID
    f.Write1UInt(8);         //payload size is 8 bytes
    f.Serialize(&off, 8);    //payload

    //TODO: Keyframe::m_block_number is a 4-byte
    //number, and we serialize all 4 bytes.  However,
    //it's unlikely we'll have block numbers that large
    //(because we create a new cluster every second).
    //Right now we always decide statically how many
    //bytes to serialize (we're using 4 bytes of storage,
    //so we serialize all 4 bytes, irrespective of the
    //value at run-time), but in the future we
    //could decide to check at run-time how large a value
    //we have, and then only serialize the minimum number
    //of bytes required for that value.
    
    f.WriteID2(0x5378);               //CueBlockNumber
    f.Write1UInt(4);                  //payload size
    f.Serialize(&m_block_number, 4);  //payload

#ifdef _DEBUG
    const __int64 stop_pos = f.GetPosition();
    assert((stop_pos - start_track_pos) == 20);
    assert((stop_pos - start_pos) == 28);
#endif
}



void CStreamIO::FinalSegment()
{
    const __int64 pos = GetPosition();  //end of clusters
    
    WriteCues();
    
    const __int64 maxpos = GetPosition();
    
    SetSize(maxpos);

    SetPosition(m_segment_pos + 4);
    
    const __int64 size = maxpos - m_segment_pos - 12;
    assert(size >= 0);
    
    Write8UInt(size);  //total size of the segment

    FinalSeekHead();    
    FinalInfo();    
    FinalClusters(pos);
}


void CStreamIO::FinalInfo()
{
    SetPosition(m_duration_pos + 2 + 4, STREAM_SEEK_SET);  //2 byte ID + 4 byte size
    
    const float duration = static_cast<float>(m_max_timecode_ms);
    
    Serialize(m_pStream, &duration, 4);
}


void CStreamIO::FinalClusters(__int64 pos)
{
    //pos represents the position of the next cluster
    
    m_clusters.push_back(Cluster());
    
    {
        Cluster& c = m_clusters.back();
        c.m_pos = pos;
    }
    
    ULONG prev = 0;
    
    clusters_t::iterator i = m_clusters.begin();
    
    for (;;)
    {
        Cluster& c = *i;
        
        if (c.m_pos == pos)
            break;
        
        c.Final(*this, prev, *++i);
        
        m_clusters.pop_front();
    }
}


void CStreamIO::Cluster::Final(
    CStreamIO& f, 
    ULONG& prev,
    const Cluster& next)
{
    const __int64 size_ = next.m_pos - m_pos;
    assert(size_ >= 8);
    assert(size_ <= ULONG_MAX);
    
    const ULONG new_prev = static_cast<ULONG>(size_);
    const ULONG size = new_prev - 8;
    
    //TODO: seek to ID, and read it from the stream, and verify it
    
    f.SetPosition(m_pos + 4);
    f.Write4UInt(size);
    
    //TODO: move to pos of Prev element, and set its value
    
    prev = new_prev;
}
    

void CStreamIO::InitSeekHead()
{
    m_seekhead_pos = GetPosition();
    
    //Let's try to write a cluster about every second.
    //We need to figure out about how may clusters we'll need.
    //The longest movie I think I've seen is Lawrence of Arabia,
    //which imdb.com is telling me is 227 min, or 13620 sec.
    //The director's cut of Cleopatra is a whopping 320 min, 
    //or 19200 sec.  So let's round up to 20000 as the max
    //number of seek entries.  (It's a pity we must do this,
    //but whatever.  We could make an optimization by using
    //IMediaSeeking to return the duration of the stream.)
    
    //The SeekID is 2 + 1 + 4 = 7 bytes.
    //The SeekPos is 2 + 1 + 8 = 11 bytes.
    //Total payload for a seek entry is 7 + 11 = 18 bytes.
    
    //The Seek entry is 2 + 1 + 18 = 21 bytes.
    //Total payload for SeekHead is 20000 * 21 = 420000 bytes.
    
    //A void element is 1 + 4 + len.
    //For now the Seek payload will be 1 + 4 + 420000 = 420005.
    
    WriteID4(m_pStream, 0x114D9B74);    //Seek Head
    Write4UInt(420005);
    
    //The size of the SeekHead element is now fixed.  The
    //entries within payload part of the SeekHead will need 
    //adjusting later.  But the total size itself
    //is determined apriori (unlike the case for, say, the 
    //Segment element).
    
    //Note that we are giving outselves a budget of 20000
    //entries.  Once we spend our budget, we won't be able
    //be able to write any more level-1 elements to the file.
    //(Or we could write them, but not put mention them
    //in the SeekHead element.)
    
    //Note also that our SeekHead element will always contain
    //at least one Void element.  (In fact, it should contain
    //exactly one Void element.)  We have budgeted for this
    //when we specified the size of the SeekHead element.
    
    //TODO: add a CRC to this element.

#ifdef _DEBUG    
    const __int64 oldpos = GetPosition();
#endif
    
    //We don't have the data yet, so we'll void it for now.

    WriteID1(0xEC);    //Void ID
    WriteData4Void(420000);
    
#ifdef _DEBUG
    const __int64 newpos = GetPosition();
    assert(newpos - oldpos == 420005);
#endif
}


void CStreamIO::FinalSeekHead()
{
    //TODO: it is permissible to write more than one seek head element.
    //That would mean we could have a small seek head up front,
    //comprising just 4 entries, for the info, track, cue, and other the seek head,
    //and we could have the other seek head have entries for just the clusters.
    //Alternatively, we could include the position of the first cluster
    //in the first seek head.
    
    //TODO: another possibility is that we've already budgeted a large
    //amount of data, we could see if we have enough space to put the 
    //cues up front.
    
    //TODO: splitter considerations: an async source can take a long
    //time to download, especially if this is a network stream.
    //We wouldn't have an index of keyframes until we've downloaded
    //the entire file (becuase the cues go at the end).  The benefit
    //of having cluster entries up front is that that would give us
    //a defacto index early.  Even without cues, you could get
    //rough indexing early.  You wouldn't get every keyframe (you
    //need the cues for that), but you can get something.  However,
    //if this is a network source, you can't really seek anyway,
    //since you don't yet have the frames!  However, we do wish
    //to minimize how much data needs to be downloaded before
    //playback can begin (even without the ability to seek),
    //so that would favor a smaller seekhead, and hence
    //fewer (but larger) clusters.  With 1ms resolution, a 
    //cluster can contain up to 32s of payload, so maybe a better
    //idea is to create clusters once every 10s or 20s or 30s,
    //instead of once every 1s as we do now.
    
    //Another option is to write the first cluster into the 
    //first seekhead (near the front of the file), and the remaining 
    //clusters into the second seekhead (near the back of the file).
    //That would minimize the amount of data required to begin
    //playback of the file.  (Really, who cares about the position
    //of clusters, except for the first one.  If you're really
    //interested in seeking, you're going to use the cues, not
    //the seekhead.)
    
    //Yet another option is to write two cues into the file.  The
    //first one containing a single entry, for the first keyframe
    //(at tc=0), and then have another cues at the end, with the
    //remaing keyframes.  Is this permitted?
    
    const __int64 start_pos = SetPosition(m_seekhead_pos + 8, STREAM_SEEK_SET);
    
    WriteSeekEntry(0x1549A966, m_info_pos);   //Segment Info
    WriteSeekEntry(0x1654AE6B, m_track_pos);  //Track
    
    typedef clusters_t::const_iterator iter_t;

    iter_t i = m_clusters.begin();
    const iter_t j = m_clusters.end();
    
    while (i != j)
    {
        const Cluster& c = *i++;        
        WriteSeekEntry(0x1F43B675, c.m_pos);
    }
    
    WriteSeekEntry(0x1C53BB6B, m_cues_pos);
    
    const __int64 stop_pos = GetPosition();
    
    const __int64 size_ = 420000 - (stop_pos - start_pos);
    assert(size_ >= 0);
    
    const ULONG size = static_cast<ULONG>(size_);
    
    WriteID1(0xEC);  //Void ID
    WriteData4Void(size);
}


void CStreamIO::WriteSeekEntry(ULONG id, __int64 pos_)
{
    assert(id & 0x10000000);  //4 bytes
    assert(id <= 0x1FFFFFFE);

    //The SeekID is 2 + 1 + 4 = 7 bytes.
    //The SeekPos is 2 + 1 + 8 = 11 bytes.
    //Total payload for a seek entry is 7 + 11 = 18 bytes.
    
    //The Seek entry is 2 + 1 + 18 = 21 bytes.
    //Total payload for SeekHead is 20000 * 21 = 420000 bytes.

#ifdef _DEBUG    
    const __int64 start_pos = GetPosition();
#endif
    
    WriteID2(0x4DBB);  //Seek Entry ID
    Write1UInt(18);    //payload size of this Seek Entry
    
    WriteID2(0x53AB);  //SeekID ID
    Write1UInt(4);     //payload size is 4 bytes
    Serialize(&id, 4); //payload
    
    const __int64 pos = pos_ - m_segment_pos - 12;
    assert(pos >= 0);
    
    WriteID2(0x53AC);    //SeekPos ID
    Write1UInt(8);       //payload size is 8 bytes
    Serialize(&pos, 8);  //payload
    
#ifdef _DEBUG
    const __int64 stop_pos = GetPosition();
    assert((stop_pos - start_pos) == 21);
#endif
}


void CStreamIO::InitInfo()
{
    m_info_pos = GetPosition();
    
    WriteID4(0x1549A966);  //Segment Info ID
    
    const __int64 pos = SetPosition(4, STREAM_SEEK_CUR);
    
    WriteID3(0x2AD7B1);
    WriteData4UInt(1000000);  //1ms resolution

    m_duration_pos = GetPosition();
        
    WriteID2(0x4489);
    WriteData4Float(0);  //patch later
    
    //MuxingApp  //TODO
    //WritingApp  //TODO
    
    const __int64 newpos = GetPosition();

    const __int64 size_ = newpos - pos;
    const ULONG size = static_cast<ULONG>(size_);
    
    SetPosition(pos - 4, STREAM_SEEK_SET);
    Write4UInt(size);
    
    SetPosition(newpos, STREAM_SEEK_SET);
}


void CStreamIO::WriteTrack()
{
    m_track_pos = GetPosition();
    
    WriteID4(m_pStream, 0x1654AE6B);  //tracks (level 1)
    
    const __int64 track_pos = SetPosition(4, STREAM_SEEK_CUR);
    
    WriteID1(m_pStream, 0xAE);  //track entry (level 2)
    
    const __int64 video_track_pos = SetPosition(4, STREAM_SEEK_CUR);
    
    WriteID1(0xD7);     //track number
    WriteData4UInt(1);  //for video use track number 1
    
    const time_t time_ = time(0);
    const unsigned seed = static_cast<unsigned>(time_);
    srand(seed);
    
    const TrackUID_t uid = CreateTrackUID();
    
    WriteID2(0x73C5);    //TrackUID ID
    Write1UInt(8);       //payload size is 8 bytes
    Serialize(&uid, 8);  //payload

    WriteID1(0x83);     //track type
    WriteData4UInt(1);  //1=video
    
    WriteID2(0x536E);   //name
    WriteData4UTF8(L"MPEG-2 video");
    
    WriteID1(0x86);  //Codec ID
    WriteData4String("V_MPEG2");
    
    assert(!m_hdr.empty());
    
    const BYTE* const ha = &m_hdr[0];    

    const std::vector<BYTE>::size_type hn_ = m_hdr.size();
    const ULONG hn = static_cast<ULONG>(hn_);
    
    WriteID2(0x63A2);  //Codec Private
    WriteData4Binary(ha, hn);
    
    WriteID3(0x258688);  //Codec Name
    WriteData4UTF8(L"MPEG-2");

    WriteID1(0xE0);  //video settings
    
    const __int64 video_settings_pos = SetPosition(4, STREAM_SEEK_CUR);
    
    binary* const data = new binary[hn];
    memcpy(data, ha, hn);
    
    MPEGChunk chunk(data, hn);
    
    MPEG2SequenceHeader h;
    ParseSequenceHeader(&chunk, h);

    WriteID1(0xB0);  //width
    WriteData4UInt(h.width);
    
    WriteID1(0xBA);  //height
    WriteData4UInt(h.height);
    
    //TODO: the BMIH tells us whether the stride is different from width;
    //is that the pixel crop values here?

    //TODO:    
    //mkvmerge sets to Interlaced flag for the video track to 0.
    //Is this value determined from h.progressiveSequence?
    
    //aspect ratio = 4:3
    //720:480 = 1.5 = 3:2 = 4.5:3
    //720 x factor / 480 = 4:3
    //factor = (4/3) * (480/720) = 
    
    WriteID3(0x2383E3);  //frame rate
    WriteData4Float(h.frameRate);    

    const __int64 newpos = GetPosition();
    
    ULONG size = static_cast<ULONG>(newpos - video_settings_pos);

    SetPosition(video_settings_pos - 4, STREAM_SEEK_SET);
    Write4UInt(size);  //complete video settings (within video track)
    
    size = static_cast<ULONG>(newpos - video_track_pos);
    
    SetPosition(video_track_pos - 4, STREAM_SEEK_SET);
    Write4UInt(size);  //complete video track
    
    size = static_cast<ULONG>(newpos - track_pos);
    
    SetPosition(track_pos - 4, STREAM_SEEK_SET);
    Write4UInt(size);  //complete track
    
    SetPosition(newpos, STREAM_SEEK_SET);
}


void CStreamIO::WriteData4Binary(
    ISequentialStream* pStream,
    const void* data, 
    ULONG size)
{
    Write4UInt(pStream, size);
    Write(pStream, data, size);
}


void CStreamIO::WriteData4Binary(const void* data, ULONG size)
{
    WriteData4Binary(m_pStream, data, size);
}


void CStreamIO::WriteData4String(
    ISequentialStream* pStream,
    const char* str)
{
    const size_t size_ = strlen(str);    
    const ULONG size = static_cast<ULONG>(size_);
    
    //TODO: it's not clear from the spec whether the nul terminator
    //should be serialized too.  For now we omit the null terminator.
    
    WriteData4Binary(pStream, str, size);
}


void CStreamIO::WriteData4String(const char* str)
{
    WriteData4String(m_pStream, str);
}


void CStreamIO::WriteData4UTF8(
    ISequentialStream* pStream,
    const wchar_t* str)
{
    assert(str);
    
    const int cb = WideCharToMultiByte(
                    CP_UTF8,
                    0, 
                    str,
                    -1,
                    0,  //buf
                    0,  //count
                    0,
                    0);

    assert(cb > 0);
    
    char* const buf = (char*)_alloca(cb);
    
    const int n = WideCharToMultiByte(
                    CP_UTF8,
                    0, 
                    str,
                    -1,
                    buf, 
                    cb,
                    0,
                    0);
                    
    assert(n == cb);
    
    //TODO: it's not clear from the spec whether the nul terminator
    //should be serialized too.  For now we include it.
    
    WriteData4Binary(pStream, buf, n);
}


void CStreamIO::WriteData4UTF8(const wchar_t* str)
{
    return WriteData4UTF8(m_pStream, str);
}


void CStreamIO::WriteData4UInt(
    ISequentialStream* pStream, 
    ULONG val)
{
    Write4UInt(pStream, 4);
    
    const BYTE* const p = (BYTE*)&val;
    const BYTE* const q = p + 4;
    
    Serialize(pStream, p, q);
}


void CStreamIO::WriteData4UInt(ULONG val)
{
    WriteData4UInt(m_pStream, val);
}


void CStreamIO::WriteData4Float(
    ISequentialStream* pStream, 
    float val)
{
    Write4UInt(pStream, 4);
    
    const BYTE* const p = (BYTE*)&val;
    const BYTE* const q = p + 4;
    
    Serialize(pStream, p, q);
}


void CStreamIO::WriteData4Float(float val)
{
    WriteData4Float(m_pStream, val);
}


void CStreamIO::WriteData4Void(
    IStream* pStream, 
    ULONG n)
{
    Write4UInt(pStream, n);
    SetPosition(pStream, n, STREAM_SEEK_CUR);
}


void CStreamIO::WriteData4Void(ULONG n)
{
    WriteData4Void(m_pStream, n);    
}


CStreamIO::TrackUID_t CStreamIO::CreateTrackUID()
{
    TrackUID_t result;
    
    BYTE* p = reinterpret_cast<BYTE*>(&result);
    BYTE* const q = p + 8;
    
    while (p != q)
    {
        const int n = rand();
        *p++ = static_cast<BYTE>(n >> 4);
    }
    
    return result;
}


__int64 CStreamIO::GetDuration() const
{
    return 10000 * __int64(m_max_timecode_ms);
}


} //end namespace MkvMux
